package org.kolonitsky.css.cssdoc;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kolonitsky.css.CssToXmlDocHandler;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.SelectorList;

import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Alex.Kolonitsky
 */
public class CssDocXmlHandler extends CssToXmlDocHandler {

    @Nullable
    private String section;

    private LinkedList<Multimap<String, String>> comments = new LinkedList<Multimap<String, String>>();

    public CssDocXmlHandler(@NotNull Writer outputFile) {
        super(outputFile);
    }

    public CssDocXmlHandler(String fileName) throws IOException {
        super(fileName);
    }

    @Override
    public void comment(String text) throws CSSException {
        try {
            this.comments.push(parseTags(cleanComment(text)));
        } catch (XMLStreamException e) {
            e.printStackTrace();  // TODO
        }
    }

    @Override
    public void startSelector(SelectorList selectors) throws CSSException {
        if (!comments.isEmpty()) {
            try {
                writeSections(findCommentsByTag("section"));
                writeSubSection(findCommentsByTag("subsection"));
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
        }

        super.startSelector(selectors);

        if (!comments.isEmpty()) {
            writeComment(comments.pop());
        }
    }

    private void writeSubSection(List<Multimap<String, String>> subsection) throws XMLStreamException {
        for (Multimap<String, String> comment : subsection) {
            writer.writeStartElement("subsection");
            writer.writeAttribute("name", joinString(comment.removeAll("subsection")));

            writeCommentTags(comment);

            writer.writeEndElement();
        }
    }

    private String joinString(Collection<String> names) {
        StringBuilder res = new StringBuilder();
        for (String name : names) {
            res.append(name).append(", ");
        }
        return res.substring(0, res.length()-2);
    }

    private void writeSections(List<Multimap<String, String>> sections) throws XMLStreamException {
        for (Multimap<String, String> comment : sections) {
            if (this.section != null) {
                writer.writeEndElement();
            }

            for (String section : comment.get("section")) {
                this.section = section.trim();
            }

            writer.writeStartElement("section");
            writer.writeAttribute("name", this.section);

            comments.remove(comment);
            writeComment(comment);
        }
    }

    @NotNull
    private List<Multimap<String, String>> findCommentsByTag(String tagName) throws XMLStreamException {
        List<Multimap<String, String>> res = new ArrayList<Multimap<String, String>>();
        for (int i = 0, lastCommentSize = comments.size(); i < lastCommentSize; i++) {
            Multimap<String, String> comment = comments.get(i);
            if (comment.containsKey(tagName)) {
                res.add(comment);
            }
        }

        return res;
    }

    private void writeComment(Multimap<String, String> lastComment) {
        try {
            writer.writeStartElement("description");

            writeCommentTags(lastComment);

            writer.writeEndElement();
        } catch (XMLStreamException e) {
            e.printStackTrace();
        }
    }

    private void writeCommentTags(Multimap<String, String> lastComment) throws XMLStreamException {
        for (Map.Entry<String, String> pair : lastComment.entries()) {
            String value = pair.getValue().trim();
            if (value.length() > 0) {
                writer.writeStartElement(pair.getKey());
                writer.writeCharacters(value);
                writer.writeEndElement();
            }
        }
    }

    @Override
    protected void startProperty() throws XMLStreamException {
        if (!comments.isEmpty()) {
            writer.writeStartElement("property");
        } else {
            writer.writeEmptyElement("property");
        }
    }

    @Override
    public void property(String name, LexicalUnit value, boolean important) throws CSSException {
        super.property(name, value, important);

        if (!comments.isEmpty()) {
            try {
                writeComment(comments.pop());
                writer.writeEndElement();
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
        }
    }

    protected static String cleanComment(String text) {
        StringBuilder builder = new StringBuilder();
        for (String line: text.split("\n")) {
            builder.append(line.replaceFirst("^(\\s*|/\\*)\\*/?", ""));
        }

        return builder.toString();
    }

    protected static Multimap<String, String> parseTags(String comment) throws XMLStreamException {
        Pattern pattern = Pattern.compile("@(\\w+)\\s+([^@]*)(?=@|$)");
        Matcher matcher = pattern.matcher("@comment" + comment);


        Multimap<String, String> map = ArrayListMultimap.create();
        while (matcher.find()) {
            map.put(matcher.group(1), matcher.group(2));
        }

        return map;
    }
}
